﻿using System.Linq;
using System.Text.RegularExpressions;

namespace Borg
{
    public abstract class SettingProvider : RuntimeElement
    {
        protected static readonly Regex NewLineRegex = new Regex("\\\\n");
        protected string path;

        static SettingProvider()
        { }

        protected SettingProvider(Runtime runtime, string path)
            : base(runtime)
        {
            this.path = path;
        }

        protected internal virtual void Init()
        { }

        protected internal abstract OrderedDictionary<ExtensionType,
            SettingDictionary> LoadSettings(Plugin plugin);

        protected internal abstract void SaveSettings(Plugin plugin,
            OrderedDictionary<ExtensionType, SettingDictionary> settings);

        protected virtual void UpdateSettings(Plugin plugin,
            OrderedDictionary<ExtensionType, SettingDictionary> settings)
        {
            foreach (var extensionType in plugin.ExtensionTypes.
                Where(extensionType => SettingStore.ContainsSettings(extensionType) &&
                                       !settings.ContainsKey(extensionType)))
                SettingStore.GetSettings(extensionType).Clear();

            foreach (var pair in settings)
            {
                var key = pair.Key;
                var settingDictionary = pair.Value;

                if (!SettingStore.ContainsSettings(key))
                    SettingStore.SetSettings(key, settingDictionary);
                else
                    UpdateDictionary(SettingStore.GetSettings(key), settingDictionary);
            }
        }

        protected virtual void ClearSettings(Plugin plugin)
        {
            foreach (var extensionType in plugin.ExtensionTypes.
                Where(extensionType => SettingStore.ContainsSettings(extensionType)))
                SettingStore.GetSettings(extensionType).Clear();
        }

        private static void UpdateDictionary(SettingDictionary originalDicitionary,
            SettingDictionary newDicitionary)
        {
            originalDicitionary.BeginUpdate();

            foreach (var key in originalDicitionary.ToArray().
                Select(pair => pair.Key).
                Where(key => !newDicitionary.Contains(key)))
                originalDicitionary.InternalRemove(key);

            foreach (var pair in newDicitionary)
            {
                var key = pair.Key;
                var val = pair.Value;

                if (!originalDicitionary.InternalContains(key))
                    originalDicitionary[key] = val;
                else
                {
                    var origDict = originalDicitionary[key];

                    if (origDict is SettingDictionary && val is SettingDictionary)
                        UpdateDictionary((SettingDictionary) origDict, (SettingDictionary) val);
                    
                    else if (origDict is SettingList && val is SettingList)
                        UpdateList((SettingList) origDict, (SettingList) val);
                    
                    else if (!Equals(origDict, val))
                        originalDicitionary[key] = val;
                }
            }

            originalDicitionary.EndUpdate();
        }

        private static void UpdateList(SettingList originalList, SettingList newList)
        {
            originalList.BeginUpdate();
            var enumerator = originalList.ToArray().GetEnumerator();

            foreach (var list in newList)
            {
                while (enumerator.MoveNext() && !Equals(enumerator.Current, list))
                    originalList.RemoveValue(enumerator.Current);

                if (enumerator.Current != null)
                    continue;

                if (list is SettingCollection)
                    originalList.InternalAddCollection((SettingCollection)list);
                else
                    originalList.InternalAddValue(list);
            }

            originalList.EndUpdate();
        }
    }
}
